// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package lockedfile

import (
	"fmt"
	"os"
	"sync"
)

// 互斥锁通过锁定一个
// 已知文件在进程内和进程间提供互斥。这样的文件通常会保护
// 文件系统的其他部分：例如，目录中的互斥文件可能会保护对
// 根在该目录中的整个树的访问。
// 
// 互斥不实现同步。不同于同步。互斥锁，一个被锁定的文件。互斥锁
// 可能无法锁定（例如，如果文件系统中存在权限错误）。像同步一样。互斥体，互斥体可以包含在更大结构的字段中，但首次使用后不得复制
// 互斥体。路径字段必须在第一次
// 使用之前设置，之后不得更改。
type Mutex struct {
	Path string     // 著名锁文件的路径。必须为非空。
	mu   sync.Mutex // 一个冗余互斥锁。种族检测器不知道文件锁定，所以在测试中我们可能需要锁定它理解的东西。
}

// MutexAt返回一个新的互斥体，其路径设置为给定的非空路径。
func MutexAt(path string) *Mutex {
	if path == "" {
		panic("lockedfile.MutexAt: path must be non-empty")
	}
	return &Mutex{Path: path}
}

func (mu *Mutex) String() string {
	return fmt.Sprintf("lockedfile.Mutex(%s)", mu.Path)
}

// Lock尝试锁定互斥锁。
// 
// 如果成功，Lock将返回一个非零解锁函数：它作为
// 返回值提供，而不是一个单独的方法来提醒调用方检查
// 伴随的错误。（见https:
func (mu *Mutex) Lock() (unlock func(), err error) {
	if mu.Path == "" {
		panic("lockedfile.Mutex: missing Path during Lock")
	}

	// 我们可以在这里使用O_RDWR或O_WRONLY。如果我们选择O_RDWR和mu的
	// 文件。Path为“仅写”，对OpenFile的调用将失败，并出现
	// 权限错误。这实际上就是我们想要的：如果我们将来添加一个RLock方法
	// 它应该用O_RDONLY调用OpenFile，并且将要求
	// 文件必须是可读的，所以我们不应该让调用者对互斥体使用只写文件做出任何假设。
	f, err := OpenFile(mu.Path, os.O_RDWR|os.O_CREATE, 0666)
	if err != nil {
		return nil, err
	}
	mu.mu.Lock()

	return func() {
		mu.mu.Unlock()
		f.Close()
	}, nil
}
